﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace BYServer.Ping
{
    public class PingClient
    {      

            const int SOCKET_ERROR = -1;
            const int ICMP_ECHO = 8;
            public static int PingHost(IPAddress ip,int port)
            {
                //----------------------------------------------//
                            
                int iPingBytes = 32;
                uint lngPingCount = 4;
                uint lngPingTimeout = 1000;
                
                //----------------------------------------------//


                const int MAX_PACKET_SIZE = 65535;
                //Declare the IPHostEntry 
                IPHostEntry PingTarget, PingSource;
                int iBytesReceived = 0;
                int dwStart = 0, dwStop = 0;
                uint iLoop = 0;
                bool bContinuous = false;
                //Random ICMP identification.
                // This fixes the bug where this ICMP would listen to all ICMP transmits
                Random rndIdent = new Random();
                ushort iIdent = (ushort)rndIdent.Next(ushort.MaxValue);
                IPEndPoint ipepServer = new IPEndPoint(ip, port);
                EndPoint epServer = (ipepServer);

                // Set the receiving endpoint to the client machine
                PingSource = Dns.GetHostByName(Dns.GetHostName());
                IPEndPoint ipEndPointFrom = new IPEndPoint(PingSource.AddressList[0], 0);
                EndPoint EndPointFrom = (ipEndPointFrom);

                Console.WriteLine("\nPinging [{0}:{1}] with {2} bytes of data:\n", ip.ToString(),
                  port.ToString(), iPingBytes);

                //check for continuous
                if (lngPingCount == 0) bContinuous = true;

                //Loop the ping
                int lngPacketsSent = 0, lngPacketsReceived = 0, lngTotalTransmitTime = 0;
                int iMinTransmitTime = int.MaxValue, iMaxTransmitTime = int.MinValue;
                ushort iSeq = 0;
                do
                {
                    bool bReceived = false;

                    //Initialize a Socket of the Type ICMP
                    Socket PingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
                    //Set socket timeout, but this doesn't seem to work...
                    PingSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, (int)lngPingTimeout);
                    PingSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, (int)lngPingTimeout);

                    // Initialize the buffers. The receive buffer is the size of the
                    // ICMP header plus the IP header (20 bytes)
                    byte[] ReceiveBuffer = new byte[MAX_PACKET_SIZE];

                    dwStart = System.Environment.TickCount; // Start timing
                    //Gather stats
                    lngPacketsSent++;


                    //Create the next packet in our sequence
                    IcmpPacket PingPacket = new IcmpPacket();
                    if (!GetICMPPacket(PingPacket, iIdent, iSeq++, iPingBytes))
                    {
                        Console.WriteLine("Error in Making Packet");
                        return -1;
                    }
                    int iPacketSize = iPingBytes + 8;
                    // Move this ICMPPacket into a byte buffer
                    byte[] byteSendBuffer = new byte[iPacketSize];
                    //serialize this PingPacket into a byte array
                    int iResult = 0;
                    iResult = Serialize(PingPacket, byteSendBuffer, iPacketSize, iPingBytes);
                    if (iResult == -1)//if there is a error report it
                    {
                        Console.WriteLine("Error in Making Packet");
                        return -1;
                    }

                    //send the Packet over the socket
                    iResult = PingSocket.SendTo(byteSendBuffer, iPacketSize, SocketFlags.None, epServer);
                    if ((iResult) == SOCKET_ERROR)
                    {
                        Console.WriteLine("Socket Error cannot Send Packet");
                    }
                    //Receive the bytes
                    iBytesReceived = 0;
                    //loop while waiting checking the time of the server responding 
                    while (!bReceived)
                    {
                        try
                        {
                            iBytesReceived = PingSocket.ReceiveFrom(ReceiveBuffer, MAX_PACKET_SIZE, SocketFlags.None, ref EndPointFrom);
                        }
                        catch
                        {
                            //Console.WriteLine("Request timed out. \n{0}", e.Message);
                            Console.WriteLine("Request timed out.");
                            bReceived = false;
                            break;
                        }
                        //OK, check the ident & seq
                        int iTemp = ReceiveBuffer[25] * 256 + ReceiveBuffer[24];
                        ushort iRecIdent = (ushort)iTemp;
                        iTemp = ReceiveBuffer[27] * 256 + ReceiveBuffer[26];
                        ushort iRecSeq = (ushort)iTemp;
                        if ((iRecIdent != iIdent) || (iRecSeq != (iSeq - 1)))   //Not our packet
                        {
                            Console.WriteLine("Request timed out.");
                            bReceived = false;
                            break;
                        }


                        if (iBytesReceived == SOCKET_ERROR)
                        {
                            Console.WriteLine("Host not Responding");
                            bReceived = false;
                            break;
                        }
                        else if (iBytesReceived > 0)
                        {

                            bReceived = true;
                            dwStop = System.Environment.TickCount - dwStart; // stop timing

                            //Check for timeout
                            if (dwStop > lngPingTimeout)
                            {
                                //Console.WriteLine("Request timed out.");
                                bReceived = false;
                                System.Threading.Thread.Sleep(System.TimeSpan.FromMilliseconds(1000));
                                break;
                            }
                            //if (dwStop < 10)
                            //{
                            //    Console.WriteLine("Reply from {0}: bytes: {1} time:<10ms ({2})", ipepServer.Address.ToString(), iBytesReceived - 28, iRecSeq);
                            //}
                            //else
                            //{
                            //    Console.WriteLine("Reply from {0}: bytes: {1} time: {2}ms ({3})", ipepServer.Address.ToString(), iBytesReceived - 28, dwStop, iRecSeq);
                            //}
                            break;
                        }
                    }//while

                    //Gather stats
                    if (bReceived)
                    {
                        lngPacketsReceived++;
                        lngTotalTransmitTime += dwStop;
                        if (dwStop > iMaxTransmitTime) iMaxTransmitTime = dwStop;
                        if (dwStop < iMinTransmitTime) iMinTransmitTime = dwStop;
                    }
                    iLoop++;

                    if (bReceived &
                      (bContinuous | (iLoop < lngPingCount)))           //not last ping
                    {
                        System.Threading.Thread.Sleep(System.TimeSpan.FromMilliseconds(1000));
                    }
                    //close the socket
                    PingSocket.Shutdown(SocketShutdown.Both);
                    PingSocket.Close();
                } while (bContinuous | (iLoop < lngPingCount));  //Do


                //Report stats
                //Console.WriteLine("\nPing statistics for {0}", ipepServer.Address.ToString());
                //if (lngPacketsSent == 0)
                //{
                //    Console.WriteLine("    Packets: Sent = {0}, Received = {1}, Lost = {2} ({3}% loss),", lngPacketsSent, lngPacketsReceived, lngPacketsSent - lngPacketsReceived, 0);
                //}
                //else
                //{
                //    Console.WriteLine("    Packets: Sent = {0}, Received = {1}, Lost = {2} ({3}% loss),", lngPacketsSent, lngPacketsReceived, lngPacketsSent - lngPacketsReceived, ((double)(lngPacketsSent - lngPacketsReceived) / lngPacketsSent) * 100);
                //}
                //Console.WriteLine("Approximate round trip times in milli-seconds:");
                //if (lngPacketsReceived == 0)
                //{
                //    Console.WriteLine("    Minimum = {0}ms, Maximum =  {1}ms, Average =  {2}ms", 0, 0, 0);
                //}
                //else
                //{
                //    Console.WriteLine("    Minimum = {0}ms, Maximum =  {1}ms, Average =  {2}ms", iMinTransmitTime, iMaxTransmitTime, (int)((double)(lngTotalTransmitTime / lngPacketsReceived)));
                //}
                return (int)(lngPacketsSent - lngPacketsReceived);
            }


            /// <summary>
            ///  This method creates an ICMP Packet
            /// </summary>
            static bool GetICMPPacket(IcmpPacket PingPacket, ushort Identifier, ushort SequenceNumber, int PingBytes)
            {
                int iPacketSize = 0;

                // Construct the packet to send
                PingPacket.Type = ICMP_ECHO; //8
                PingPacket.SubCode = 0;
                PingPacket.CheckSum = UInt16.Parse("0");
                PingPacket.Identifier = Identifier;
                PingPacket.SequenceNumber = SequenceNumber;
                PingPacket.Data = new Byte[PingBytes];
                //Initialize the Packet.Data
                for (int iCount = 0; iCount < PingBytes; iCount++)
                {
                    PingPacket.Data[iCount] = (byte)'#';
                }

                //Variable to hold the total Packet size
                iPacketSize = PingBytes + 8;
                byte[] bytPktBuffer = new byte[iPacketSize];
                int iResult = 0;

                //Call a Method Serialize which counts the total number of Bytes in the Packet
                iResult = Serialize(PingPacket, bytPktBuffer, iPacketSize, PingBytes);
                if (iResult == -1)//Error in Packet Size
                {
                    Console.WriteLine("Error in Making Packet");
                    return false;
                }

                // now get this critter into a ushort array 
                ushort[] cksum_buffer = new ushort[Convert.ToInt32(Math.Ceiling(Convert.ToDouble(iResult) / 2))];

                //Code to initialize the ushort array 
                int icmp_header_buffer_index = 0;
                for (int iCount = 0; iCount < cksum_buffer.Length; iCount++)
                {
                    cksum_buffer[iCount] = BitConverter.ToUInt16(bytPktBuffer, icmp_header_buffer_index);
                    icmp_header_buffer_index += 2;
                }
                //Call a method which will return a checksum and save the checksum to the Packet
                PingPacket.CheckSum = CheckSum(cksum_buffer);

                return true;
            }

            /// <summary>
            ///  This method is used to get the Packet and calculates the total size
            ///  of the Pack by converting it to byte array
            /// </summary>
            static int Serialize(IcmpPacket ThisPacket, byte[] Buffer, int PacketSize, int PingData)
            {
                int cbReturn = 0;
                // serialize the struct into the array
                int iIndex = 0;

                byte[] b_type = new byte[1];
                b_type[0] = ThisPacket.Type;

                byte[] b_code = new byte[1];
                b_code[0] = ThisPacket.SubCode;

                byte[] b_cksum = BitConverter.GetBytes(ThisPacket.CheckSum);
                byte[] b_id = BitConverter.GetBytes(ThisPacket.Identifier);
                byte[] b_seq = BitConverter.GetBytes(ThisPacket.SequenceNumber);

                // Console.WriteLine("Serialize type ");
                Array.Copy(b_type, 0, Buffer, iIndex, b_type.Length);
                iIndex += b_type.Length;

                // Console.WriteLine("Serialize code ");
                Array.Copy(b_code, 0, Buffer, iIndex, b_code.Length);
                iIndex += b_code.Length;

                // Console.WriteLine("Serialize cksum ");
                Array.Copy(b_cksum, 0, Buffer, iIndex, b_cksum.Length);
                iIndex += b_cksum.Length;

                // Console.WriteLine("Serialize id ");
                Array.Copy(b_id, 0, Buffer, iIndex, b_id.Length);
                iIndex += b_id.Length;

                Array.Copy(b_seq, 0, Buffer, iIndex, b_seq.Length);
                iIndex += b_seq.Length;

                // copy the data   
                Array.Copy(ThisPacket.Data, 0, Buffer, iIndex, PingData);
                iIndex += PingData;
                if (iIndex != PacketSize/* sizeof(IcmpPacket)  */)
                {
                    cbReturn = -1;
                    return cbReturn;
                }

                cbReturn = iIndex;
                return cbReturn;
            }
            /// <summary>
            ///   Checksum - 
            ///     Algorithm to create a checksup for a buffer
            /// </summary>
            static ushort CheckSum(ushort[] BufferToChecksum)
            {
                int iCheckSum = 0;

                for (uint iCount = 0; iCount < BufferToChecksum.Length; iCount++)
                {
                    iCheckSum += Convert.ToInt32(BufferToChecksum[iCount]);
                }

                iCheckSum = (iCheckSum >> 16) + (iCheckSum & 0xffff);
                iCheckSum += (iCheckSum >> 16);
                return (ushort)(~iCheckSum);
            }

            /// <summary>
            ///   Class that holds the Pack information
            /// </summary>
            public class IcmpPacket
            {
                public byte Type;             // type of message
                public byte SubCode;          // type of sub code
                public ushort CheckSum;         // ones complement checksum of struct
                public ushort Identifier;       // identifier
                public ushort SequenceNumber;   // sequence number  
                public byte[] Data;             // byte array of data
            } // class IcmpPacket  
        
    }
}
